using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.IO;
using System.Reflection;
using Accountability4net;
using System.Xml.XPath;
using Accountability4net.Contact;
using System.Drawing;
using System.Drawing.Imaging;

namespace Accountability4net.FileMonengineProvider
{
    public class FileMonengineProvider : MonengineProviderBase
    {
        private XmlFileStorage storage_;

        public XmlFileStorage Storage
        {
            get
            {
                if (storage_ == null)
                {
                    storage_ = new XmlFileStorage();
                }
                return storage_;
            }
        }

        public override void Install()
        {
            storage_ = null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="number"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public override IAccount CreateAccount(int number, AccountType type)
        {
            if (GetAccount(number) != null)
            {
                throw new ArgumentException(String.Format("An account with number={0} already exists", number), "number");
            }
            else
            {
                FileAccount account = new FileAccount(this);
                account.Number = number;
                account.Type = type;
                account.Name = String.Empty;
                account.Description = String.Empty;
                XmlNode accountsNode = Storage.SelectSingleNode(@"x:system/x:accounts");
                account.Write(accountsNode);
                Storage.Save();
                account.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(account_PropertyChanged);
                return account;
            }
        }

        void account_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            SaveAccount(sender as FileAccount);
        }

        public void SaveAccount(IAccount account)
        {
            XmlNode node = Storage.SelectSingleNode(String.Format(@"x:system/x:accounts/x:account[@number='{0}']", account.Number));
            IAccount originalAccount = null;
            if (node != null)
            {
                originalAccount = FileAccount.Parse(node);
                node.Attributes["name"].Value = account.Name;
                node.Attributes["description"].Value = account.Description;
                node.Attributes["type"].Value = account.Type.ToString();
            }
            Storage.Save();
        }

        public override IAction CreateAction(ActionType type, IAccount account, decimal amount)
        {
            return new FileAction(type, account, amount, this);
        }

        public override ITransaction CreateTransaction(DateTimeOffset timestamp)
        {
            FileTransaction transaction = new FileTransaction(this);
            transaction.Timestamp = timestamp;
            return transaction;
        }

        public void SaveTransaction(ITransaction transaction)
        {
            if (transaction is FileTransaction)
            {
                XmlNode node = Storage.SelectSingleNode(@"x:system/x:transactions");
                (transaction as FileTransaction).Write(node);
                Storage.Save();
            }
        }

        public override IAccount GetAccount(int number)
        {
            IAccount account = null;
            XmlNode node = Storage.SelectSingleNode(String.Format(@"x:system/x:accounts/x:account[@number='{0}']", number));
            if (node != null)
            {
                account = FileAccount.Parse(node, this);
            }
            return account;
        }

        public override IList<IAccount> GetAccounts(int from, int limit)
        {
            List<IAccount> accounts = new List<IAccount>();
            XmlNodeList nodes = Storage.SelectNodes(String.Format(@"x:system/x:accounts/x:account[@number>='{0}'][position()<='{1}']", from, limit));
            foreach(XmlNode node in nodes)
            {
                accounts.Add(FileAccount.Parse(node, this));
            }
            return accounts;
        }

        public override IList<ITransaction> GetTransactions()
        {
            List<ITransaction> transactions = new List<ITransaction>();
            XmlNodeList nodes = Storage.SelectNodes(@"x:system/x:transactions/x:transaction");
            foreach (XmlNode node in nodes)
            {
                transactions.Add(FileTransaction.Parse(node, this));
            }
            return transactions;
        }

        public override IList<ITransaction> GetTransactions(IAccount account)
        {
            List<ITransaction> transactions = new List<ITransaction>();
            XmlNodeList nodes = Storage.SelectNodes(String.Format(@"x:system/x:transactions/x:transaction[x:action[@account = {0}]]", account.Number));
            foreach (XmlNode node in nodes)
            {
                transactions.Add(FileTransaction.Parse(node, this));
            }
            return transactions;
        }

        public override IList<ITransaction> GetTransactions(IAccount account, DateTimeOffset? after)
        {
            List<ITransaction> transactions = new List<ITransaction>(GetTransactions(account));
            if (after.HasValue)
            {
                return transactions.FindAll(
                    delegate(ITransaction transaction)
                    {
                        return transaction.Timestamp >= after.Value;
                    }
                );
            }
            return transactions;
        }

        public override IList<ITransaction> GetTransactions(IAccount account, DateTimeOffset? after, DateTimeOffset? before)
        {
            List<ITransaction> transactions = new List<ITransaction>(GetTransactions(account, after));
            if (before.HasValue)
            {
                return transactions.FindAll(
                    delegate(ITransaction transaction)
                    {
                        return transaction.Timestamp < before.Value;
                    }
                );
            }
            return transactions;
        }

        public override void Dispose()
        {
            // No operation
        }

        public override IContact CreateContact()
        {
            FileContact contact = new FileContact(this);
            contact.ID = Guid.NewGuid();
            XmlNode contactsNode = Storage.SelectSingleNode(@"x:system/x:contacts");
            contact.Write(contactsNode);
            Storage.Save();
            contact.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(contact_PropertyChanged);
            ((FileName)contact.Name).PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(name_PropertyChanged);
            return contact;
        }

        void contact_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            SaveContact(sender as FileContact);
        }

        void name_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            SaveContact((sender as FileName).Contact);
        }

        public void SaveContact(IContact contact)
        {
            XmlNode node = Storage.SelectSingleNode(String.Format(@"x:system/x:contacts/x:contact[@id='{0}']", contact.ID));
            IContact originalContact = null;
            if (node != null)
            {
                originalContact = FileContact.Parse(node, this);
                node.Attributes["id"].Value = contact.ID.ToString();
                XmlNode emailNode = Storage.SelectSingleNode(node, @"x:email");
                emailNode.InnerText = contact.Email;
                ((FileName)contact.Name).Write(node);
                if (contact.Photo != null)
                {
                    XmlUtility.CreateImageElement(node, "photo", contact.Photo);
                }

            }
            Storage.Save();
        }

        public override IContact GetContact(Guid id)
        {
            IContact contact = null;
            XmlNode node = Storage.SelectSingleNode(String.Format(@"x:system/x:contacts/x:contact[@id='{0}']", id));
            if (node != null)
            {
                contact = FileContact.Parse(node, this);
            }
            return contact;            
        }

    }
}
